;; This test has two components $C and $D where $D imports and calls $C
;; $C implements two resource types imported and used by $D
;; $D creates instances of both resource types, uses them and destroys them
(component
  (component $C
    (core module $Indirect
      (table (export "ftbl") 2 funcref)
      (type $FT (func (param i32)))
      (func (export "R1-dtor") (param i32)
        (call_indirect (type $FT) (local.get 0) (i32.const 0))
      )
      (func (export "R2-dtor") (param i32)
        (call_indirect (type $FT) (local.get 0) (i32.const 1))
      )
    )
    (core instance $indirect (instantiate $Indirect))
    (type $R1' (resource (rep i32) (dtor (func $indirect "R1-dtor"))))
    (type $R2' (resource (rep i32) (dtor (func $indirect "R2-dtor"))))
    (export $R1 "R1" (type $R1'))
    (export $R2 "R2" (type $R2'))
    (canon resource.new $R1' (core func $R1.resource.new))
    (canon resource.new $R2' (core func $R2.resource.new))

    (core module $CM
      (import "" "ftbl" (table 1 funcref))
      (import "" "R1.resource.new" (func $R1.resource.new (param i32) (result i32)))
      (import "" "R2.resource.new" (func $R2.resource.new (param i32) (result i32)))
      (memory 1)
      (global $num-live-R1 (mut i32) (i32.const 0))
      (global $num-live-R2 (mut i32) (i32.const 0))

      ;; constructors
      (func $make-R1 (export "make-R1") (result i32)
        (local $h i32)
        (global.set $num-live-R1 (i32.add (global.get $num-live-R1) (i32.const 1)))
        (call $R1.resource.new (i32.add (i32.const 0x40) (global.get $num-live-R1)))
      )
      (func $make-R2 (export "make-R2") (result i32)
        (local $h i32)
        (global.set $num-live-R2 (i32.add (global.get $num-live-R2) (i32.const 1)))
        (call $R2.resource.new (i32.add (i32.const 0x80) (global.get $num-live-R2)))
      )

      ;; accessors
      (func $get-rep-R1 (export "get-rep-R1") (param $rep i32) (result i32)
        (local.get $rep)
      )
      (func $get-rep-R2 (export "get-rep-R2") (param $rep i32) (result i32)
        (local.get $rep)
      )

      ;; destructors
      (func $R1-dtor (param $rep i32)
        (if (i32.or (i32.lt_u (local.get $rep) (i32.const 0x41)) (i32.gt_u (local.get $rep) (i32.const 0x42)))
          (then unreachable))
        (if (i32.eqz (global.get $num-live-R1))
          (then unreachable))
        (global.set $num-live-R1 (i32.sub (global.get $num-live-R1) (i32.const 1)))
      )
      (func $R2-dtor (param $rep i32)
        (if (i32.or (i32.lt_u (local.get $rep) (i32.const 0x81)) (i32.gt_u (local.get $rep) (i32.const 0x82)))
          (then unreachable))
        (if (i32.eqz (global.get $num-live-R2))
          (then unreachable))
        (global.set $num-live-R2 (i32.sub (global.get $num-live-R2) (i32.const 1)))
      )
      (func $num-live (export "num-live") (result i32)
        (i32.add (global.get $num-live-R1) (global.get $num-live-R2))
      )
      (elem (i32.const 0) $R1-dtor $R2-dtor)
    )
    (core instance $cm (instantiate $CM (with "" (instance
      (export "ftbl" (table $indirect "ftbl"))
      (export "R1.resource.new" (func $R1.resource.new))
      (export "R2.resource.new" (func $R2.resource.new))
    ))))
    (func $make-R1 (export "make-R1") (result (own $R1)) (canon lift (core func $cm "make-R1")))
    (func $make-R2 (export "make-R2") (result (own $R2)) (canon lift (core func $cm "make-R2")))
    (func $get-rep-R1 (export "get-rep-R1") (param "r" (borrow $R1)) (result u32) (canon lift (core func $cm "get-rep-R1")))
    (func $get-rep-R2 (export "get-rep-R2") (param "r" (borrow $R2)) (result u32) (canon lift (core func $cm "get-rep-R2")))
    (func (export "num-live") (result u32) (canon lift (core func $cm "num-live")))
  )

  (component $D
    (import "c" (instance $c
      (export "R1" (type $R1 (sub resource)))
      (export "R2" (type $R2 (sub resource)))
      (export "make-R1" (func (result (own $R1))))
      (export "make-R2" (func (result (own $R2))))
      (export "get-rep-R1" (func (param "r" (borrow $R1)) (result u32)))
      (export "get-rep-R2" (func (param "r" (borrow $R2)) (result u32)))
      (export "num-live" (func (result u32)))
    ))
    (core module $DM
      (import "" "R1.resource.drop" (func $R1.resource.drop (param i32)))
      (import "" "R2.resource.drop" (func $R2.resource.drop (param i32)))
      (import "" "make-R1" (func $make-R1 (result i32)))
      (import "" "make-R2" (func $make-R2 (result i32)))
      (import "" "get-rep-R1" (func $get-rep-R1 (param i32) (result i32)))
      (import "" "get-rep-R2" (func $get-rep-R2 (param i32) (result i32)))
      (import "" "num-live" (func $num-live (result i32)))
      (memory 1)

      (func $run (export "run") (result i32)
        (local $ret i32)
        (local $h1 i32) (local $h2 i32) (local $h3 i32) (local $h4 i32)

        ;; create 4 resources
        (local.set $h1 (call $make-R1))
        (if (i32.ne (i32.const 2) (local.get $h1))
          (then unreachable))
        (local.set $h2 (call $make-R2))
        (if (i32.ne (i32.const 3) (local.get $h2))
          (then unreachable))
        (local.set $h3 (call $make-R1))
        (if (i32.ne (i32.const 4) (local.get $h3))
          (then unreachable))
        (local.set $h4 (call $make-R2))
        (if (i32.ne (i32.const 5) (local.get $h4))
          (then unreachable))
        (if (i32.ne (i32.const 4) (call $num-live))
          (then unreachable))

        ;; use and destroy resources
        (if (i32.ne (i32.const 0x81) (call $get-rep-R2 (local.get $h2)))
          (then unreachable))
        (call $R2.resource.drop (local.get $h2))
        (if (i32.ne (i32.const 0x41) (call $get-rep-R1 (local.get $h1)))
          (then unreachable))
        (call $R1.resource.drop (local.get $h1))
        (if (i32.ne (i32.const 0x82) (call $get-rep-R2 (local.get $h4)))
          (then unreachable))
        (call $R2.resource.drop (local.get $h4))
        (if (i32.ne (i32.const 0x42) (call $get-rep-R1 (local.get $h3)))
          (then unreachable))
        (call $R1.resource.drop (local.get $h3))

        ;; everything should be destroyed
        (if (i32.ne (i32.const 0) (call $num-live))
          (then unreachable))

        (i32.const 42)
      )
    )
    (alias export $c "R1" (type $R1))
    (alias export $c "R2" (type $R2))
    (canon resource.drop $R1 (core func $R1.resource.drop))
    (canon resource.drop $R2 (core func $R2.resource.drop))
    (canon lower (func $c "make-R1") (core func $make-R1'))
    (canon lower (func $c "make-R2") (core func $make-R2'))
    (canon lower (func $c "get-rep-R1") (core func $get-rep-R1'))
    (canon lower (func $c "get-rep-R2") (core func $get-rep-R2'))
    (canon lower (func $c "num-live") (core func $num-live'))
    (core instance $dm (instantiate $DM (with "" (instance
      (export "R1.resource.drop" (func $R1.resource.drop))
      (export "R2.resource.drop" (func $R2.resource.drop))
      (export "make-R1" (func $make-R1'))
      (export "make-R2" (func $make-R2'))
      (export "get-rep-R1" (func $get-rep-R1'))
      (export "get-rep-R2" (func $get-rep-R2'))
      (export "num-live" (func $num-live'))
    ))))
    (func (export "run") (result u32) (canon lift (core func $dm "run")))
  )

  (instance $c (instantiate $C))
  (instance $d (instantiate $D (with "c" (instance $c))))
  (func (export "run") (alias export $d "run"))
)
(assert_return (invoke "run") (u32.const 42))
